/***************************************************
*		 Copyright (c) 2018 MINE 田宇
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
***************************************************/

#include "module.h"
#include "memory.h"
#include "lib.h"
#include "VFS.h"
#include "task.h"
#include "errno.h"
#include "ELF.h"
#include "printk.h"

struct module modules = 
{
	.init = NULL,
	.exit = NULL,
	.module_name = "Kernel_modules",
	.list = {&modules.list,&modules.list},
};

unsigned long find_kernel_symbol(char * name)
{
	int i = 0;
	int count = _kernel_symbol_end - _kernel_symbol_begin;
	struct kernel_symbol *ptr = _kernel_symbol_begin;

	for(i = 0;i<count;i++,ptr++)
	{
		if(!strcmp(name,ptr->name))
			return ptr->address;
	}

	return 0;
}


int apply_relocate_add(struct module * mod,Elf64_Shdr *shdr_rela,Elf64_Ehdr *ehdr)
{
	int i = 0;
	char * location = NULL;
	long value = 0;
	Elf64_Shdr *shdr = NULL;
	Elf64_Rela *relaptr = (Elf64_Rela *)(mod->module_file + shdr_rela->sh_offset);
	shdr = (Elf64_Shdr *)(mod->module_file + ehdr->e_shoff);

	for(i = 0;i<shdr_rela->sh_size/shdr_rela->sh_entsize;i++,relaptr++)
	{
		switch(relaptr->r_info & 0xffffffff)
		{
			case R_X86_64_NONE:
				break;

			case R_X86_64_64:
				location = mod->module_file + (shdr +  shdr_rela->sh_info)->sh_offset + relaptr->r_offset;
				value = ((Elf64_Sym *)(mod->module_file + (shdr + shdr_rela->sh_link)->sh_offset) + (relaptr->r_info >> 32))->st_value + relaptr->r_addend;
				*(unsigned long*)location = value;
				break;

			case R_X86_64_32:
				location = mod->module_file + (shdr +  shdr_rela->sh_info)->sh_offset + relaptr->r_offset;
				value = ((Elf64_Sym *)(mod->module_file + (shdr + shdr_rela->sh_link)->sh_offset) + (relaptr->r_info >> 32))->st_value + relaptr->r_addend;
				*(unsigned int*)location = value;
				break;

			case R_X86_64_32S:
				location = mod->module_file + (shdr +  shdr_rela->sh_info)->sh_offset + relaptr->r_offset;
				value = ((Elf64_Sym *)(mod->module_file + (shdr + shdr_rela->sh_link)->sh_offset) + (relaptr->r_info >> 32))->st_value + relaptr->r_addend;
				*(int*)location = value;
				break;

			case R_X86_64_PC32:
				location = mod->module_file + (shdr +  shdr_rela->sh_info)->sh_offset + relaptr->r_offset;
				value = ((Elf64_Sym *)(mod->module_file + (shdr + shdr_rela->sh_link)->sh_offset) + (relaptr->r_info >> 32))->st_value + relaptr->r_addend;
				*(unsigned int*)location = value - (long)location;
				break;

			default:
				break;
		}
	}

	return 0;
}


struct module *find_module(char* modname)
{
	struct module *modp = container_of(modules.list.next,struct module,list);

	do
	{
		color_printk(WHITE,BLACK,"list mode name:%s\n",modp->module_name);
		color_printk(WHITE,BLACK,"mode name:%s\n",modname);
		if(!strcmp(modp->module_name,modname))
			return modp;
		modp = container_of(modp->list.next,struct module,list);
		color_printk(WHITE,BLACK,"list mode name:%s\n",modp->module_name);
	}while(modp->list.next != &modules.list);

	return NULL;
}

struct module * analysis_module_Ehdr(char * filebuffer)
{
	int i = 0;
	Elf64_Ehdr *ehdr = (Elf64_Ehdr *)filebuffer;
	Elf64_Shdr *shdr = NULL;
	Elf64_Shdr *shstrdr = NULL;
	struct module * modules = NULL;

	color_printk(WHITE,BLACK,"%#x ",ehdr->e_ident[0]);
	for(i = 1;i<4;i++)
		color_printk(WHITE,BLACK,"%c",ehdr->e_ident[i]);

	color_printk(WHITE,BLACK," Class32/64:%s ",ehdr->e_ident[4]-1?"Class64":"Class32");
	color_printk(WHITE,BLACK,"LSB/MSB:%s ",ehdr->e_ident[5]-1?"MSB":"LSB");
	color_printk(WHITE,BLACK,"File Version:%d ",ehdr->e_ident[6]);

	if(ehdr->e_type == 1)
		color_printk(WHITE,BLACK,"Type:Relocatable File ");

	if(ehdr->e_machine == 64)
		color_printk(WHITE,BLACK,"MACHINE:Machine:x86-64 ");

	color_printk(WHITE,BLACK,"Version:%d ",ehdr->e_version);
	color_printk(WHITE,BLACK,"Entry:%#x ",ehdr->e_entry);
	color_printk(WHITE,BLACK,"Size of ELF Header:%d\n",ehdr->e_ehsize);


	color_printk(BLUE,BLACK,"Program header offset:%d\t",ehdr->e_phoff);
	color_printk(BLUE,BLACK,"Size of Program Header:%d\t",ehdr->e_phentsize);
	color_printk(BLUE,BLACK,"Number of Program Header:%d\n",ehdr->e_phnum);

	color_printk(YELLOW,BLACK,"Section header offset:%d\t",ehdr->e_shoff);
	color_printk(YELLOW,BLACK,"Size of Section Header:%d\t",ehdr->e_shentsize);
	color_printk(YELLOW,BLACK,"Number of Section Header:%d\n",ehdr->e_shnum);

	color_printk(WHITE,BLACK,"Index of String table Section:%d\t",ehdr->e_shstrndx);

	shdr = (Elf64_Shdr *)(filebuffer + ehdr->e_shoff);
	shstrdr = shdr + ehdr->e_shstrndx;

	for(i = 0;i<ehdr->e_shnum;i++)
	{
		if(!strcmp(((char*)ehdr + shstrdr->sh_offset + shdr->sh_name),".this_module"))
		{
			modules = (struct module *)(filebuffer + shdr->sh_offset);
			modules->ehdr = ehdr;
			modules->shdr = (Elf64_Shdr *)(filebuffer + ehdr->e_shoff);
			modules->shstrdr = shstrdr;
			modules->shsymtab = NULL;
			modules->module_file = filebuffer;
			list_init(&modules->list);
			return modules;
		}
		shdr++;
	}

	return NULL;
}

void analysis_module_Shdr(struct module *mod)
{
	int i = 0;
	Elf64_Ehdr *ehdr = mod->ehdr;
	Elf64_Shdr *shdr = mod->shdr;
	Elf64_Shdr *shstrdr = mod->shstrdr;

	color_printk(GREEN,BLACK,"Type  Offset  Size  ENT size  LINK  INFO\n");

	for(i = 0;i<ehdr->e_shnum;i++,shdr++)
	{
		if(shdr->sh_type == SHT_NULL)
			continue;
		color_printk(GREEN,BLACK,"(%02d)\"%20s\" ",i,((char*)ehdr + shstrdr->sh_offset + shdr->sh_name));
		switch(shdr->sh_type)
		{
			case SHT_NULL:
				color_printk(GREEN,BLACK,"SHT_NULL\t");
				break;

			case SHT_PROGBITS:
				color_printk(GREEN,BLACK,"SHT_PROGBITS\t");
				break;

			case SHT_SYMTAB:
				color_printk(ORANGE,BLACK,"SHT_SYMTAB\t");
				mod->shsymtab = shdr;	
				break;

			case SHT_STRTAB:
				color_printk(GREEN,BLACK,"SHT_STRTAB\t");
				break;

			case SHT_RELA:
				color_printk(ORANGE,BLACK,"SHT_RELA\t");
				break;

			case SHT_HASH:
				color_printk(GREEN,BLACK,"SHT_HASH\t");
				break;

			case SHT_DYNAMIC:
				color_printk(GREEN,BLACK,"SHT_DYNAMIC\t");
				break;

			case SHT_NOTE:
				color_printk(GREEN,BLACK,"SHT_NOTE\t");
				break;

			case SHT_NOBITS:
				color_printk(GREEN,BLACK,"SHT_NOBITS\t");
				break;

			case SHT_REL:
				color_printk(ORANGE,BLACK,"SHT_REL\t");
				break;

			case SHT_SHLIB:
				color_printk(GREEN,BLACK,"SHT_SHLIB\t");
				break;

			case SHT_DYNSYM:
				color_printk(GREEN,BLACK,"SHT_DYNSYM\t");
				break;

			case SHT_NUM:
				color_printk(GREEN,BLACK,"SHT_NUM\t");
				break;

			case SHT_LOPROC:
				color_printk(GREEN,BLACK,"SHT_LOPROC\t");
				break;

			case SHT_HIPROC:
				color_printk(GREEN,BLACK,"SHT_HIPROC\t");
				break;

			case SHT_LOUSER:
				color_printk(GREEN,BLACK,"SHT_LOUSER\t");
				break;

			case SHT_HIUSER:
				color_printk(GREEN,BLACK,"SHT_HIUSER\t");
				break;

			default:
				color_printk(RED,BLACK,"Type Error\t");
				break;
		}
		color_printk(GREEN,BLACK,"%#010x %08d %08d %#010x %#010x\n",shdr->sh_offset,shdr->sh_size,shdr->sh_entsize,shdr->sh_link,shdr->sh_info);
	}
}


void analysis_module_Sym(struct module *mod)
{
	int i = 0;
	Elf64_Shdr *shsymtab = mod->shsymtab;
	Elf64_Sym *symptr = (Elf64_Sym *)(mod->module_file + shsymtab->sh_offset);
	Elf64_Shdr *shdr = mod->shdr;

	color_printk(PURPLE,BLACK,"  Value  \t  Info(B,T)   Other  SHindex  \t  Value  \t  Size\n");

	for(i = 0;i < shsymtab->sh_size / shsymtab->sh_entsize;i++,symptr++)
	{
		if(symptr->st_shndx == 0xfff1 || symptr->st_shndx == 0xfff2)
			continue;

		color_printk(PURPLE,BLACK,"(%02d)%#010x ",i,symptr->st_value);

		switch(symptr->st_info >> 4 & 0xf)
		{
			case 0:
				color_printk(PURPLE,BLACK," Local,");
				break;

			case 1:
				color_printk(PURPLE,BLACK,"Global,");
				break;

			case 2:
				color_printk(PURPLE,BLACK,"  Weak,");
				break;

			default:
				break;
		}

		switch(symptr->st_info & 0xf)
		{
			case 0:
				color_printk(PURPLE,BLACK," NoType ");
				break;

			case 1:
				color_printk(PURPLE,BLACK," Object ");
				break;

			case 2:
				color_printk(PURPLE,BLACK,"   Func ");
				break;

			case 3:
				color_printk(PURPLE,BLACK,"Section ");
				break;

			case 4:
				color_printk(PURPLE,BLACK,"   File ");
				break;

			default:
				break;
		}

		color_printk(PURPLE,BLACK,"%04d ",symptr->st_other);

		switch(symptr->st_shndx)
		{
			case 0:	
				color_printk(WHITE,BLACK,"%#010x",symptr->st_value);			
				symptr->st_value = find_kernel_symbol(mod->module_file + (shdr + shsymtab->sh_link)->sh_offset + symptr->st_name);
				color_printk(RED,BLACK,"%016lx ",symptr->st_value);
				break;

			case 0xfff1:
				color_printk(PURPLE,BLACK,"   ABS ");
				break;

			case 0xfff2:
				color_printk(PURPLE,BLACK,"COMMON ");
				break;

			default:
				if(symptr->st_value)
					color_printk(YELLOW,BLACK,"%#010x",symptr->st_value);
				else
					color_printk(WHITE,BLACK,"%#010x",symptr->st_value);
				symptr->st_value += (unsigned long)(mod->module_file + (shdr + symptr->st_shndx)->sh_offset);
				color_printk(INDIGO,BLACK,"%016lx ",symptr->st_value);
				break;
		}
		if(symptr->st_size)
			color_printk(GREEN,BLACK,"%#010x ",symptr->st_size);
		else
			color_printk(PURPLE,BLACK,"%#010x ",symptr->st_size);

		color_printk(PURPLE,BLACK,"\"%20s\"\n",mod->module_file + (shdr + shsymtab->sh_link)->sh_offset + symptr->st_name);
	}
}

void analysis_module_Rela(struct module *mod)
{
	int i = 0;
	Elf64_Ehdr *ehdr = mod->ehdr;
	Elf64_Shdr *shdr = mod->shdr;

	for(i = 0;i<ehdr->e_shnum;i++)
	{
		if(shdr->sh_type == SHT_RELA)
			apply_relocate_add(mod,shdr,ehdr);
		shdr++;
	}
}


unsigned long sys_init_module(int fd)
{
	struct file * filp = NULL;
	struct module * mod = NULL;
	char * module_file = NULL;
	long i = 0;

	if(fd < 0 || fd >= TASK_FILE_MAX)
		return -EBADF;

	filp = current->file_struct[fd];
	module_file = (char *)kmalloc(filp->dentry->dir_inode->file_size,0);
	filp->f_ops->read(filp,module_file,filp->dentry->dir_inode->file_size,&i);

	mod = analysis_module_Ehdr(module_file);
	if(mod == NULL)
		return -EIO;

	analysis_module_Shdr(mod);
	analysis_module_Sym(mod);
	analysis_module_Rela(mod);

	if(find_module(mod->module_name))
	{
		kfree(mod->module_file);
		return -EEXIST;
	}
	list_add_to_behind(&modules.list,&mod->list);

	color_printk(WHITE,BLACK,"address of the color_printk:%#018lx,%s\n",&color_printk,mod->module_name);
	mod->init();

	return 0;
}


unsigned long sys_delete_module(char * mod_name)
{
	struct module * mod = NULL;

	mod = find_module(mod_name);
	if(mod)
	{
		list_del(&mod->list);
		mod->exit();
		kfree(mod->module_file);
		kfree(mod);
	}
	else
		return -EINVAL;

	return 0;
}



